/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.codegenerator;

import com.googlecode.datawander.codecompiler.CodeCompiler;
import com.googlecode.datawander.i18n.I18n;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import com.googlecode.datawander.shemareader.MetaClass;
import com.googlecode.datawander.shemareader.MetaClassField;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;

/**
 *
 * @author Marcin Stachniuk
 */
public class SourceCodeGenerator2Object {

    private static final Logger logger = Logger.getLogger(SourceCodeGenerator2Object.class);

    /**
     * Header of source file
     */
    public static final String SOURCE_FILE_HEADER;

    /**
     * New line char. Use to generate new line in file.
     */
    public static final String NEW_LINE;
    
    /**
     * String with one ident in files with source code
     */
    private static final String INDENT = "    ";

    /**
     * String use to insert into querys before and after table and field names.
     */
    public static final String ESC_CHAR = "\\\"";

    // static inicialize block
    static {
        NEW_LINE = System.getProperty("line.separator");

        SOURCE_FILE_HEADER = "/* This file was generated automatically by DataWander."+NEW_LINE+
                    " * Author of DataWander: Marcin Stachniuk"+NEW_LINE+
                    " * Project Page: http://code.google.com/p/datawander/"+NEW_LINE+
                    " */"+NEW_LINE;
    }

    private List<MetaClass> metaClassList;

    /**
     * Generate source code from MetaClass
     * @param metaClassList List of MetaClass from there generate souce code
     * files
     * @param pathName Path to output directory
     */
    public void generateSourceCode(List<MetaClass> metaClassList, String pathName) throws CodeGenerationException {
        logger.trace("generateSourceCode("+metaClassList+", "+pathName+")");

        this.metaClassList = metaClassList;

        File file = new File(pathName);
        file.mkdir();

        if(!file.isDirectory()) {
            String message = I18n.getString("thispathisnotadirectiory");
            message = String.format(message, file.getAbsolutePath());
            throw new CodeGenerationException(message);
        }

        cleanSourceDirectory(pathName);

        for(MetaClass mc : metaClassList) {
            generateSourceCode(mc, file.getPath()+File.separatorChar+mc.getClassName()+".java");
        }

        DatabasesConnectorsGenerator db4oGenerator = new DatabasesConnectorsGenerator();
        db4oGenerator.generateDb4oUtilSource(getClassNamesFromMetaClassList(metaClassList), pathName);

        OracleConnectorGenerator oracleGenerator = new OracleConnectorGenerator();
        oracleGenerator.generateOracleConnector(pathName);

        logger.info(I18n.getString("codegeneratesucess"));
    }

    private List<String> getClassNamesFromMetaClassList(List<MetaClass> metaClassList) {
        List<String> list = new ArrayList<String>();
        for(MetaClass mc : metaClassList) {
            list.add(mc.getClassName());
        }
        return list;
    }

    /**
     * Generate sourcecode for MetaClass to fileName file.
     * @param metaClass Object with info about generated class
     * @param fileName Path to outputfile
     */
    private void generateSourceCode(MetaClass metaClass, String fileName) throws CodeGenerationException {
        logger.trace("generateSourceCode("+metaClass+", "+fileName+")");

        File file = new File(fileName);
        try {
            file.createNewFile();
        } catch (IOException ex) {
            logger.error(ex, ex);
            throw new CodeGenerationException(ex.getMessage());
        }
        try {
            PrintWriter out = new PrintWriter(file);

            // generation head od file
            out.append(SOURCE_FILE_HEADER);

            // generate class name
            out.append("public class "+metaClass.getClassName()+" ");

            //TODO: Sprawdzenie czy klasa dziedziczy po czymś

            out.append("{"+NEW_LINE + NEW_LINE);

            // generate fields
            generateFields(metaClass, out);

            //methods generation
            generateLoadAllRecordsMethodFromRelationalDatabase(metaClass, out);

            //generateSaveAllRecordsInDb4o(metaClass, out);
            generateSaveAllUniqueRecordsInDb4o(metaClass, out);

            generateUpdateTableObjectMethod(metaClass, out);

            generateLoadForeginKeysMethods(metaClass, out);

            if(metaClass.isAssociationClass()) {
                generateUpdateManyToManyObjectsMethod(metaClass, out);
                generateDeleteAllObjectsMethod(metaClass, out);
            }

            generateGettersAndSettersForPrimaryKeys(metaClass, out);

            // getters and setters generation
            generateGettersAndSetters(metaClass, out);

            // toString() method generation
            generateToStringMethod(metaClass, out);

            generateHashCodeMethod(metaClass, out);

            generateEqualsNoDuplicateInDb4oMethod(metaClass, out);

            // ended bracket
            out.append("}"+NEW_LINE);
            out.close();

            String message = I18n.getString("filegenerated");
            message = String.format(message, file.getName());
            logger.info(message);
        } catch (FileNotFoundException ex) {
            logger.error(ex, ex);
            throw new CodeGenerationException(
                    I18n.getString("codegenerationexceptionproblem"));
        }

    }

    /**
     * Append to PrintWriter deklaration of class fields.
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateFields(MetaClass metaClass, PrintWriter out) {
        logger.trace("generateFields("+metaClass+", "+out+")");

        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);
            if(field.isPrimaryKey()) {
                Integer [] filterTab = filterFieldsToGenerateCode(field);
//                for(int k : filterTab) {
//                    System.out.println("to generate field: "+k + " tab: " +
//                            field.getPrimaryKeyReferenceTable()[k] + " column: " +
//                            field.getPrimaryKeyReferenceColumn()[k]);
//                }
                for(int j=0; j < filterTab.length; j++) {
                    generateFieldForPrimaryKey(metaClass, field, filterTab[j], out);
                }
            }
            out.append(INDENT + "private " + field.getType() + " " +
                    field.getValueName() + ";" + NEW_LINE);
        }

        out.append(NEW_LINE);
    }

    /**
     * Filter fields for that create fields and methods in generated class.
     * This metod reject second field from "one class associacion".
     * @param field That is primary key.
     * @return Numbers idnexs that will be used to generate fields and methods
     * in class.
     */
    private Integer [] filterFieldsToGenerateCode(MetaClassField field) {
        Set<String> mySet = new HashSet<String>();
        Set<Integer> results = new HashSet<Integer>();
        for(int j=0; j < field.getPrimaryKeyReferenceTable().length; j++) {
            String refTable = field.getPrimaryKeyReferenceTable()[j];
            if(mySet.add(refTable)) {
                results.add(j);
            } else {
                logger.debug("filterFieldsToGenerateCode no add: table: " + refTable +
                        " col: " + field.getPrimaryKeyReferenceColumn()[j]);
                MetaClass refClass = findClassInMetaClassList(refTable);
                if(!refClass.isToOneClassAssociacion()) {
                    results.add(j);
                    logger.debug("a jednak dodajemy :)");
                }
            }
        }
        return results.toArray(new Integer [0]);
    }

    /**
     * Generate all fields what reference to primary key.
     * @param metaClass MetaClass with info about generated class.
     * @param Primary key field for which is generate fields.
     * @param index Number of field for which is generate fields.
     * @param out Output file.
     */
    private void generateFieldForPrimaryKey(MetaClass metaClass, MetaClassField field,
            int index, PrintWriter out) {
        logger.trace("generateFieldForPrimaryKey("+metaClass.getClassName()+", "+field.getValueName()+", "+index+" ...);");
        String refColumn = field.getPrimaryKeyReferenceColumn()[index];
        String refName = field.getPrimaryKeyReferenceName()[index];
        String refTable = field.getPrimaryKeyReferenceTable()[index];


        MetaClassField foreignField = findForeginKeyInMetaClassList(refTable, refColumn);
        if(foreignField.isUnique()) {
            out.append(INDENT + "private " + refTable + " " + refName + ";" + NEW_LINE);
        } else {
            MetaClass foreignClass = findClassInMetaClassList(refTable);
            if(foreignClass.isAssociationClass()) {
                // M:N
                out.append(INDENT + "// field to M:N to another class" + NEW_LINE);
                for(int i=0; i < foreignClass.getFieldsNumber(); i++) {
                    MetaClassField foreignFieldFromAssocClass = foreignClass.getField(i);
                    if(!foreignFieldFromAssocClass.equals(foreignField)) {
                        out.append(INDENT + "private java.util.List<" +
                        foreignFieldFromAssocClass.getForeignFieldTable() + "> " + refName + ";" + NEW_LINE);
                    }
                }
            } else {
                // 1:N
                out.append(INDENT + "private java.util.List<" + refTable + "> " + refName + ";" + NEW_LINE);
            }
        }
    }

    /**
     * Generate all getters and setters for field metaClas
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateGettersAndSetters(MetaClass metaClass, PrintWriter out) {
        for (int i = 0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);
            String type = field.getType();
            String name = field.getValueName();

            // generate getter
            out.append(INDENT + "public " + type + (type.equals("boolean") ? " is" : " get" ) +
                    name.substring(0, 1).toUpperCase() + name.substring(1) +
                    "() { " +NEW_LINE);
            out.append(INDENT + INDENT + "return " + name + ";" + NEW_LINE);
            out.append(INDENT + "}" + NEW_LINE + NEW_LINE);

            // generate setter
            out.append(INDENT + "public void set" + firstCharToUpper(name) +
                    "(" + type + " " + name + ") { " +NEW_LINE);
            out.append(INDENT + INDENT + "this." + name + " = " + name + ";" + NEW_LINE);
            out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
        }
    }

    /**
     * Return String, where first char in name is Upper, and other chars are
     * this same.
     * @param name Strign to cnovert
     * @return Strign with first char Upper
     */
    private String firstCharToUpper(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    /**
     * Generate method to load all Records of metaClass from relational
     * database
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateLoadAllRecordsMethodFromRelationalDatabase(MetaClass metaClass, PrintWriter out) throws CodeGenerationException {
        out.append(INDENT + "public static " + metaClass.getClassName() +
                "[] loadAllRecords() throws Exception {" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.Connection conn = OracleConnector.getConnection();" +
                NEW_LINE + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.PreparedStatement stmt = conn.prepareStatement(\"select * from " +
                ESC_CHAR + metaClass.getClassName() + ESC_CHAR + "\");" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.ResultSet resultSet = stmt.executeQuery();" +
                NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "java.util.List<" + metaClass.getClassName() + 
                "> list = new com.db4o.collections.ArrayList4<" + metaClass.getClassName() +
                ">();" + NEW_LINE + NEW_LINE);
        out.append(INDENT + INDENT + "while(resultSet.next()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + metaClass.getClassName() + " obj = new " +
                metaClass.getClassName() +"();" + NEW_LINE);

        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            generateGetFieldValueFromRelationaDatabase(metaClass, out, i);
        }
        
        out.append(INDENT + INDENT + INDENT + "list.add(obj);" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "return list.toArray(new " + metaClass.getClassName() +
                "[0]);" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    /**
     * Generate invoke one method to get value from relational database. If it's
     * noraml record metod generate somethig like this:
     * obj.setID((java.math.BigDecimal)resultSet.getObject("ID"));
     * Method genereate something else if actual field is primary key or foreign
     * key.
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     * @param index Number of fields
     */
    private void generateGetFieldValueFromRelationaDatabase(MetaClass metaClass,
            PrintWriter out, int index) throws CodeGenerationException {
        logger.trace("generateGetFieldValueFromRelationaDatabase("+metaClass.getClassName()+", "+index);
        MetaClassField field = metaClass.getField(index);
        String type = field.getType();
        String name = field.getValueName();

        if(field.isPrimaryKey()) {
            out.append(INDENT + INDENT + INDENT + "// Primary Key" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + type + " " +name + " = ("
                    + type + ") resultSet.getObject(\"" + name + "\");"+ NEW_LINE);

            //TODO: generowanie metod ktore beda odczywtywac wartosci kluczy
            // obcych
            // jeśli klucz obcy jest unique to bedzie 1:1, chyba ze to tabela asocjacyjna

            for(int i=0; i < field.getPrimaryKeyReferenceTable().length; i++) {
                String refTable = field.getPrimaryKeyReferenceTable()[i];
                String refColumn = field.getPrimaryKeyReferenceColumn()[i];
                String refName = field.getPrimaryKeyReferenceName()[i];
                MetaClassField foreignField = findForeginKeyInMetaClassList(refTable, refColumn);
                if(foreignField == null) {
                    String message = I18n.getString("cantfindforeignkeyinmetaclasslist");
                    message = String.format(message, refTable, refColumn);
                    throw new CodeGenerationException(message);
                }

                if(foreignField.isUnique()) {
                    // 1:1 relation
                    out.append(INDENT + INDENT + INDENT + "obj.set"+foreignField.getForeignFieldReferenceName() +
                            "(" + refTable + ".load" + refName + "(" + name +
                            "));" + NEW_LINE);
                } else {
                    MetaClass foreginClass = findClassInMetaClassList(refTable);
                    if(foreginClass.isAssociationClass()) {
                        // relation with associacion class
                    } else {
                        // 1:N relation
                        out.append(INDENT + INDENT + INDENT + "// 1:N Relation "+NEW_LINE);
                        out.append(INDENT + INDENT + INDENT + "obj.set" +
                                foreignField.getForeignFieldReferenceName() +
                                "(" + refTable + ".load" + refName + "(" + name +
                                "));" + NEW_LINE);
                    }
                }

                //TODO: M:N realtion
            }
            out.append(NEW_LINE);

        }
        out.append(INDENT + INDENT + INDENT + "obj.set" + firstCharToUpper(name) +
                    "((" + type + ")resultSet." + generateGetMethodNameFromType(type) +
                    "(\""+ name + "\"));" + NEW_LINE);
    }

    /**
     * Metod use to generate method name to read value of column from ResultSet.
     * This metod must return a maethod name who exist in ResultSet class.
     * @param type Name of type column.
     * @return Method name to read object. Default: getObject
     */
    private String generateGetMethodNameFromType(String type) {
        if(type.equals("java.lang.Double")) {
            return "getDouble";
        } else if(type.equals("java.sql.Timestamp")) {
            return "getTimestamp";
        } else {
            // return default name
            return "getObject";
        }
    }

    /**
     * Generate toString method.
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateToStringMethod(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "@Override" + NEW_LINE);
        out.append(INDENT + "public String toString() {" + NEW_LINE);

        out.append(INDENT + INDENT + "return \"["+metaClass.getClassName()+"; \"");

        for (int i = 0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);

            String value = field.getValueName();

            out.append(" + " + NEW_LINE + INDENT + INDENT + INDENT +
                    "\"" + value + "=\" + " + value);

            if(i != metaClass.getFieldsNumber() - 1) {
                out.append(" + \", \"");        // przecinek po wartosci pola
            }
        }

        out.append(" + \"]\";" + NEW_LINE);

        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    /**
     * Generate method to save all record in object database.
     * @deprecated Now use generateSaveAllUniqueRecordsInDb4o() method.
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateSaveAllRecordsInDb4o(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "public static void saveObjectsInDb4o(" + metaClass.getClassName() + 
                "[] objectsToSave) {" + NEW_LINE);
        out.append(INDENT + INDENT + "try {"+NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "com.db4o.ObjectContainer db4o = " +
                "Db4oUtil.getObjectContainer();" + NEW_LINE);
        
        out.append(INDENT + INDENT + INDENT + "for(" + metaClass.getClassName() +
                " o : objectsToSave) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "db4o.store(o);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT +
                "System.out.println(\"Zachowano: \" + o.toString());" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE);


        out.append(INDENT + INDENT + "} catch(Exception e) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "e.printStackTrace();" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
//        out.append(INDENT + INDENT + "} finally {" + NEW_LINE);
//        out.append(INDENT + INDENT + INDENT + "Db4oUtil.closeObjectContainer();"+NEW_LINE);
//        out.append(INDENT + INDENT + "}" + NEW_LINE);
        
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);

    }

    /**
     * Generate method to save all unique record in object database.
     * Before save records program test that is this object exist.
     * @param metaClass MetaClass with info about generated class.
     * @param out Output file.
     */
    private void generateSaveAllUniqueRecordsInDb4o(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "public static void saveObjectsInDb4o(" + metaClass.getClassName() +
                "[] objectsToSave) {" + NEW_LINE);
        out.append(INDENT + INDENT + "try {"+NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "com.db4o.ObjectContainer db4o = " +
                "Db4oUtil.getObjectContainer();" + NEW_LINE);
        
        out.append(INDENT + INDENT + INDENT + "for(final " + metaClass.getClassName() +
                " o : objectsToSave) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "com.db4o.ObjectSet<" +
                metaClass.getClassName() + "> objectSet = db4o.query(new com.db4o.query.Predicate<" +
                metaClass.getClassName() + ">() {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                "@Override" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                "public boolean match(" + metaClass.getClassName() +
                " obj) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + INDENT +
                "return o.equals(obj);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "});" + NEW_LINE);
        
        out.append(INDENT + INDENT + INDENT + INDENT +
                "if(objectSet.size() == 0) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "o.updateTableObject(db4o);" +
                NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "db4o.store(o);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + 
                "System.out.println(\"Zachowano: \" + o.toString());" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "} else {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                "System.out.println(\"Bylo juz w bazie: \" + o.toString());" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE);


        out.append(INDENT + INDENT + "} catch(Exception e) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "e.printStackTrace();" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
//        out.append(INDENT + INDENT + "} finally {" + NEW_LINE);
//        out.append(INDENT + INDENT + INDENT + "Db4oUtil.closeObjectContainer();"+NEW_LINE);
//        out.append(INDENT + INDENT + "}" + NEW_LINE);

        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);

    }

    /**
     * Find foregin key in metaClassList, what reference to tableName and columnName
     * @param tableName
     * @param columnName
     * @return Fouded field or null if not found
     */
    private MetaClassField findForeginKeyInMetaClassList(String tableName, String columnName) {
        logger.trace("findForeginKeyInMetaClassList("+tableName+", "+columnName+");");
        for(MetaClass mc : metaClassList) {
            if(mc.getClassName().equals(tableName)) {
                for(int i=0; i < mc.getFieldsNumber(); i++) {
                    MetaClassField field = mc.getField(i);

                    logger.trace("field: "+field);
                    logger.trace(" field.getForeignFieldColumn(): " +
                            field.getForeignFieldColumn());
                    if(field.getValueName().equals(columnName)) {
                        return field;
                    }
                }
            }
        }
        return null;
    }

    private void generateLoadForeginKeysMethods(MetaClass metaClass, PrintWriter out) 
            throws CodeGenerationException {
        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);
            if(field.isForeignKey()) {
                if(field.isUnique()) {
                    generateForeginKeyMethodLoad1to1(metaClass, field, out);
                } else {
                    generateForeginKeyMethodLoad1toN(metaClass, field, out);
                }
            }
        }
    }

    private void generateForeginKeyMethodLoad1to1(MetaClass metaClass, 
            MetaClassField field, PrintWriter out) throws CodeGenerationException {
        String referenceName = field.getForeignFieldReferenceName();
        String columnName = field.getForeignFieldColumn();
        String columnType = field.getType();

        out.append(INDENT + "public static " + metaClass.getClassName() +
                " load" + referenceName + "(" + columnType + " " + columnName +
                ") throws Exception {" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.Connection conn = OracleConnector.getConnection();" +
                NEW_LINE + NEW_LINE);
        //java.sql.PreparedStatement stmt = conn.prepareStatement("select * from LOGIN where ID = "+ID);
        out.append(INDENT + INDENT + "java.sql.PreparedStatement stmt = " +
                "conn.prepareStatement(\"select * from " +
                metaClass.getClassName()  +" where " + field.getValueName() +
                " = \"+" + columnName +");" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.ResultSet resultSet = stmt.executeQuery();" +
                NEW_LINE + NEW_LINE);

//        out.append(INDENT + INDENT + "java.util.List<" + metaClass.getClassName() +
//                "> list = new java.util.ArrayList<" + metaClass.getClassName() + ">();" + NEW_LINE + NEW_LINE);
        out.append(INDENT + INDENT + metaClass.getClassName() + " obj = null;" + NEW_LINE);
        out.append(INDENT + INDENT + "if(resultSet.next()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "obj = new "+ metaClass.getClassName() +
                "();" + NEW_LINE);

        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            generateGetFieldValueFromRelationaDatabase(metaClass, out, i);
        }

        //out.append(INDENT + INDENT + INDENT + "list.add(obj);" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "return obj;" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateForeginKeyMethodLoad1toN(MetaClass metaClass,
            MetaClassField field, PrintWriter out) throws CodeGenerationException {
        String referenceName = field.getForeignFieldReferenceName();
        String columnName = field.getForeignFieldColumn();
        String columnType = field.getType();

        out.append(INDENT + "public static java.util.List<" + metaClass.getClassName() +
                "> load" + referenceName + "(" + columnType + " " + columnName +
                ") throws Exception {" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.Connection conn = " +
                "OracleConnector.getConnection();" + NEW_LINE + NEW_LINE);
        //java.sql.PreparedStatement stmt = conn.prepareStatement("select * from LOGIN where ID = "+ID);
        out.append(INDENT + INDENT + "java.sql.PreparedStatement stmt = " +
                "conn.prepareStatement(\"select * from " +
                metaClass.getClassName()  +" where " + field.getValueName() +
                " = \"+" + columnName +");" + NEW_LINE);
        out.append(INDENT + INDENT + "java.sql.ResultSet resultSet = stmt.executeQuery();" +
                NEW_LINE + NEW_LINE);


        out.append(INDENT + INDENT + "java.util.List<" + metaClass.getClassName() +
                "> list = new com.db4o.collections.ArrayList4<" + metaClass.getClassName() +
                ">();" + NEW_LINE);
        out.append(INDENT + INDENT + "while(resultSet.next()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + metaClass.getClassName() + " obj = new " +
                metaClass.getClassName() +"();" + NEW_LINE);

        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            generateGetFieldValueFromRelationaDatabase(metaClass, out, i);
        }

        out.append(INDENT + INDENT + INDENT + "list.add(obj);" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "return list;" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateGettersAndSettersForPrimaryKeys(MetaClass metaClass, PrintWriter out) {
        for (int i = 0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);
            if(field.isPrimaryKey()) {

                Integer [] filterTab = filterFieldsToGenerateCode(field);
                for(int k : filterTab) {
                    System.out.println("to generate field: "+k + " tab: " +
                            field.getPrimaryKeyReferenceTable()[k] + " column: " +
                            field.getPrimaryKeyReferenceColumn()[k]);
                }

                for (int j = 0; j < filterTab.length; j++) {

                    String type = field.getType();
                    String name = field.getValueName();

                    String refName = field.getPrimaryKeyReferenceName()[filterTab[j]];
                    String refTable = field.getPrimaryKeyReferenceTable()[filterTab[j]];
                    String refColumn = field.getPrimaryKeyReferenceColumn()[filterTab[j]];

                    MetaClass foreginClass = findClassInMetaClassList(refTable);
                    if(foreginClass.isAssociationClass()) {
                        out.append(INDENT + "// getters i setters for associacion class" + NEW_LINE);

                        for(int k=0; k < foreginClass.getFieldsNumber(); k++) {
                            MetaClassField foreignFieldFromAssocClass = foreginClass.getField(k);

                            if(!foreignFieldFromAssocClass.getValueName().equals(refColumn)) {
                                // generate add method
                                out.append(INDENT + "public void add" + refName +
                                 "(" + foreignFieldFromAssocClass.getForeignFieldTable() +
                                 " o1) {" + NEW_LINE);
                                out.append(INDENT + INDENT + "if(" + refName +
                                        " == null) {" + NEW_LINE);
                                out.append(INDENT + INDENT + INDENT + refName +
                                        " = new com.db4o.collections.ArrayList4<" +
                                        foreignFieldFromAssocClass.getForeignFieldTable() +
                                        ">();" + NEW_LINE);
                                out.append(INDENT + INDENT + "}" + NEW_LINE);
                                out.append(INDENT + INDENT + refName +
                                        ".add(o1);" + NEW_LINE);
                                out.append(INDENT + "}" + NEW_LINE);

                                //generate get method
                                out.append(INDENT + "public java.util.List<" +
                                        foreignFieldFromAssocClass.getForeignFieldTable() +
                                        "> get" + refName + "() {" + NEW_LINE);
                                out.append(INDENT + INDENT + "return " + refName +
                                        ";" + NEW_LINE);
                                out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
                            }
                        }
                    } else {        // no associacion class
                        MetaClassField foreginField = findForeginKeyInMetaClassList(
                                refTable, refColumn);
                        if(foreginField.isUnique()) {
                            // generate setter
                            out.append(INDENT + "// generate unique for: "+j +NEW_LINE);
                            out.append(INDENT + "public void set" + refName +
                                    "(" + refTable + " " + name + ") { " +NEW_LINE);
                            out.append(INDENT + INDENT + "this." + refName + " = " +
                                    name + ";" + NEW_LINE);
                            out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
                        } else {
                            // generate setter
                            out.append(INDENT + "// generate non unique for: " +
                                    j + NEW_LINE);
                            out.append(INDENT + "public void set" + refName +
                                    "(java.util.List<" + refTable + "> " + name +
                                    ") { " +NEW_LINE);
                            out.append(INDENT + INDENT + "this." + refName + " = " +
                                    name + ";" + NEW_LINE);
                            out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
                        }
                    }
                }
            }

        }
    }

    private void cleanSourceDirectory(String pathName) {
        CodeCompiler cleanCodeFolder = new CodeCompiler();
        cleanCodeFolder.cleanDirectory(pathName);
//        File file = new File(pathName);
//        File [] files = file.listFiles(new FileFilter() {
//            public boolean accept(File pathname) {
//                if(!pathname.isDirectory() && pathname.getName().endsWith(".java")) {
//                    if(pathname.getName().equals("OracleConnector.java")) {
//                        return false;
//                    }
//                    return true;
//                }
//                return false;
//            }
//        });
//
//        for(File f : files) {
//            String message = I18n.getString("cleansourcecodedirectory");
//            message = String.format(message, f.getName());
//            logger.debug(message);
//            f.delete();
//        }
    }

    private void generateHashCodeMethod(MetaClass metaClass, PrintWriter out) {
        if(metaClass.getNumberOfPrimaryKeys() == 0) {
            // hash code return sum of all fields
            out.append(INDENT + "@Override" + NEW_LINE);
            out.append(INDENT + "public int hashCode() {" + NEW_LINE);
            out.append(INDENT + INDENT + "return ");
            for(int i=0; i < metaClass.getFieldsNumber(); i++) {
                if(i >= 1) {
                        out.append(INDENT + INDENT + INDENT + INDENT);
                    }
                MetaClassField field = metaClass.getField(i);
                out.append("(this." + field.getValueName() + " != null ? this." +
                        field.getValueName()+".hashCode() : 0)");
                if(i == (metaClass.getFieldsNumber() - 1)) {
                    // last field
                    out.append(";" + NEW_LINE);
                } else {
                    out.append(" + " + NEW_LINE);
                }
            }
            out.append(INDENT + "}" + NEW_LINE);
        } else {
            // hash code only for primary keys
            out.append(INDENT + "@Override" + NEW_LINE);
            out.append(INDENT + "public int hashCode() {" + NEW_LINE);
            out.append(INDENT + INDENT + "return ");

            StringBuilder sb = new StringBuilder();
            for(int i=0; i < metaClass.getFieldsNumber(); i++) {
                MetaClassField field = metaClass.getField(i);
                if(field.isPrimaryKey()) {
                    if(i >= 1) {
                        sb.append(INDENT + INDENT + INDENT + INDENT);
                    }
                    sb.append("(this." + field.getValueName() + " != null ? this." +
                            field.getValueName() + ".hashCode() : 0) + " + NEW_LINE);
                }
            }
            String str = sb.substring(0, sb.lastIndexOf(" + "+NEW_LINE));

            out.append(str);
            out.append(";" + NEW_LINE);
            out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
        }
    }

    private void generateEqualsNoDuplicateInDb4oMethod(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "@Override" + NEW_LINE);
        out.append(INDENT + "public boolean equals(Object obj) {" + NEW_LINE);
        out.append(INDENT + INDENT + "if (obj == null) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "return false;" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + "if (getClass() != obj.getClass()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "return false;" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + "final " + metaClass.getClassName() + 
                " other = (" + metaClass.getClassName() + ") obj;" + NEW_LINE);

        if(metaClass.getNumberOfPrimaryKeys() == 0) {
            // check all fields
            for(int i=0; i < metaClass.getFieldsNumber(); i++) {
                MetaClassField field = metaClass.getField(i);
                generateEualsConditionForField(metaClass, field, out);
            }
        } else {
            // check only primary keys
            for(int i=0; i < metaClass.getFieldsNumber(); i++) {
                MetaClassField field = metaClass.getField(i);
                if(field.isPrimaryKey()) {
                    generateEualsConditionForField(metaClass, field, out);
                }
            }
        }

        out.append(INDENT + INDENT + "return true;" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE);

    }

    private void generateEualsConditionForField(MetaClass metaClass, MetaClassField field,
            PrintWriter out) {
        out.append(INDENT + INDENT + "if((this." + field.getValueName() +
                " == null) ? (other." + field.getValueName() +
                " != null) : !this." + field.getValueName() +
                ".equals(other." + field.getValueName() + ")) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "return false;" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
    }

    //TODO: refactor to generateUpdateListObjectMethod
    private void generateUpdateTableObjectMethod(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "private void updateTableObject(com.db4o.ObjectContainer db4o) {" + NEW_LINE);

        for(int i=0; i < metaClass.getFieldsNumber(); i++) {
            MetaClassField field = metaClass.getField(i);
            if(field.isPrimaryKey()) {
                for(int j=0; j < field.getPrimaryKeyReferenceColumn().length; j++) {
                    generateUpdateTableCode(metaClass, field, j, out);
                }
            }
        }

        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateUpdateTableCode(MetaClass metaClass, MetaClassField field,
            int index, PrintWriter out) {
        String refColumn = field.getPrimaryKeyReferenceColumn()[index];
        String refName = field.getPrimaryKeyReferenceName()[index];
        String refTable = field.getPrimaryKeyReferenceTable()[index];
        out.append(INDENT + INDENT + "// metaClass: " + metaClass.getClassName() +
                " field: "+field.getValueName() + " refColumn: " + refColumn
                + " refName: " + refName + " refTable: " + refTable + 
                " field unique: " + field.isUnique() + " is foreign key: " +
                field.isForeignKey() + " type: " + field.getType() + NEW_LINE);

        // metaClass: APPUSER field: ID refColumn: IDUSER refName: FK_IDUSER refTable: PHOTO_GALLERY
        MetaClassField foreignField = findForeginKeyInMetaClassList(refTable,
                refColumn);
        out.append(INDENT + INDENT + "// foreignField.isUnique() : " +
                foreignField.isUnique() + NEW_LINE);

        MetaClass foreginClass = findClassInMetaClassList(refTable);
        if(foreginClass.isAssociationClass()) {
            out.append(INDENT + INDENT + "// Class " + refTable +
                    " is associacion class, no update now" + NEW_LINE);
            return;
        }

        // isn't 1:1 relation
        if(!foreignField.isUnique()) {
            out.append(INDENT  +INDENT + "for(int i=0; i < " + refName +
                    ".size(); i++) {" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + refTable + " obj = " + refName +
                    ".get(i);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + "com.db4o.ObjectSet<" + refTable +
                    "> objectSet = db4o.queryByExample(obj);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + "while(objectSet.hasNext()) {" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + refTable +
                    " temp = objectSet.next();" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT +
                    "if(temp.equals(obj)) {" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                    "// update object" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + refName +
                    ".set(i, temp);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                    "System.out.println(\"Update object: \"+obj.toString());" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "break;" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE);
            out.append(INDENT + INDENT + "}" + NEW_LINE);
        }
    }

    /**
     * Generate source code updateManyToManyObjects(). This code is for
     * updating references beetween class if they are in M:N relation.
     * @param metaClass Object of MetaClass that must be associated class
     * @param out
     */
    public void generateUpdateManyToManyObjectsMethod(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "public static void updateManyToManyObjects() {" + NEW_LINE);
        out.append(INDENT + INDENT + "com.db4o.ObjectContainer db4o = " +
                "Db4oUtil.getObjectContainer();" + NEW_LINE);

        MetaClassField field1 = metaClass.getField(0);
        MetaClassField field2 = metaClass.getField(1);

        out.append(INDENT + INDENT + "com.db4o.ObjectSet<" + field1.getForeignFieldTable() +
                "> objects1 = db4o.queryByExample(new " + field1.getForeignFieldTable() +
                "());" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "while(objects1.hasNext()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + field1.getForeignFieldTable() +
                " o1 = objects1.next();" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "final " + field1.getType() +
                " id = o1.get" + field1.getForeignFieldColumn() + "();" + NEW_LINE + NEW_LINE);

//        out.append(INDENT + "// field1.getForeignFieldColumn() " + field1.getForeignFieldColumn()+ NEW_LINE);
//        out.append(INDENT + "// field1.getForeignFieldReferenceName() " + field1.getForeignFieldReferenceName() + NEW_LINE);
//        out.append(INDENT + "// field1.getForeignFieldReferenceTable() " + field1.getForeignFieldTable() + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "com.db4o.ObjectSet<" + metaClass.getClassName() +
                "> assocjacions = db4o.query(new com.db4o.query.Predicate<" +
                metaClass.getClassName() + ">() {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "@Override" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "public boolean match(" +
                metaClass.getClassName() + " et) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "return et.get" +
                field1.getValueName() + "().equals(id);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "});" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "final java.util.Set<" +
                field1.getType() + "> assocIDs = new java.util.TreeSet<" +
                field1.getType() + ">();" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "while(assocjacions.hasNext()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "assocIDs.add(assocjacions.next().get" +
                field2.getValueName() + "());" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "com.db4o.ObjectSet<" + field2.getForeignFieldTable() +
                "> objects2 = db4o.query(new com.db4o.query.Predicate<" + field2.getForeignFieldTable() +
                ">() {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "@Override" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "public boolean match(" +
                field2.getForeignFieldTable() + " et) {" +NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT +
                "return assocIDs.contains(et.get" + field2.getForeignFieldColumn() + "());" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "});" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "while(objects2.hasNext()) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + field2.getForeignFieldTable() +
                " o2 = objects2.next();" + NEW_LINE);


        // foreigns keys refer to the same table
        if(field1.getForeignFieldTable().equals(field2.getForeignFieldTable())) {
            //TODO: czy field2 zawsze bedzie ok?
            //MetaClass anotherClass = findClassInMetaClassList(field1.getForeignFieldTable());

            MetaClassField myField = field1;
            
            out.append(INDENT + INDENT + INDENT + INDENT + "if(o1.get" +
                    myField.getForeignFieldReferenceName() + "() == null ||" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + INDENT +
                    "(! o1.get" + myField.getForeignFieldReferenceName() +
                    "().contains(o2))) {" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "o1.add" +
                    myField.getForeignFieldReferenceName() + "(o2);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);

            out.append(INDENT + INDENT + INDENT + INDENT + "if(o2.get" +
                    myField.getForeignFieldReferenceName() + "() == null ||" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + INDENT +
                    "(! o1.get" + myField.getForeignFieldReferenceName() +
                    "().contains(o1))) {" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "o2.add" +
                    myField.getForeignFieldReferenceName() + "(o1);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);

        } else {
            out.append(INDENT + INDENT + INDENT + INDENT + "o1.add" +
                    field1.getForeignFieldReferenceName() + "(o2);" + NEW_LINE);
            out.append(INDENT + INDENT + INDENT + INDENT + "o2.add" +
                    field2.getForeignFieldReferenceName() + "(o1);" + NEW_LINE);
        }
        
        out.append(INDENT + INDENT + INDENT + INDENT + "db4o.store(o2);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "db4o.store(o1);" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);

        out.append(INDENT + INDENT + "deleteAllObjects();" + NEW_LINE);

        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private MetaClass findClassInMetaClassList(String className) {
        logger.trace("findClassInMetaClassList(" + className + ");");
        for(MetaClass mc : metaClassList) {
            if(mc.getClassName().equals(className)) {
                return mc;
            }
        }
        return null;
    }

    /**
     * Generate method that delete all objects from object database. This method
     * should be invofe for class that mapping asociacions tables.
     * @param metaClass
     * @param out
     */
    private void generateDeleteAllObjectsMethod(MetaClass metaClass, PrintWriter out) {
        out.append(INDENT + "private static void deleteAllObjects() {" + NEW_LINE);
        out.append(INDENT + INDENT + "com.db4o.ObjectContainer db4o = " +
                "Db4oUtil.getObjectContainer();" + NEW_LINE);
        out.append(INDENT + INDENT + "com.db4o.ObjectSet<" +
                metaClass.getClassName() + "> objects1 = db4o.query(" +
                metaClass.getClassName() + ".class);" + NEW_LINE + NEW_LINE);

        out.append(INDENT + INDENT + "for(" + metaClass.getClassName() +
                " o : objects1) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "db4o.delete(o);" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }



//    private void updateTableObject(ObjectContainer db4o) {
//        for(int i=0; i < FK_IDUSER.length; i++) {
//            PHOTO_GALLERY obj = FK_IDUSER[i];
//            com.db4o.ObjectSet<PHOTO_GALLERY> objectSet = db4o.queryByExample(obj);
//            while(objectSet.hasNext()) {
//                PHOTO_GALLERY temp = objectSet.next();
//                if(temp.equals(obj)) {
//                    // update object
//                    System.out.println("Zaktualizowano obiekt: "+obj.toString());
//                    FK_IDUSER[i] = temp;
//                    break;
//                }
//            }
//        }
//    }

}
